package org.dicr.util.net;

import java.io.*;

import org.dicr.util.data.*;

/**
 * IP-address.
 * 
 * @author Igor A Tarasov, &lt;java@dicr.org&gt.
 * @version 060127
 */
public final class IP implements Serializable, Cloneable, Comparable<IP> {
	private static final long serialVersionUID = 1L;

	/** Octets divider. */
	public static final char DIV = '.';

	/** IP-address <CODE>0.0.0.0</CODE> */
	public static final IP ZERO = new IP(0);

	/** IP-address <CODE>255.255.255.255</CODE> */
	public static final IP MAX = new IP(0xFFFFFFFF);

	/** address value */
	protected int value = 0;

	/**
     * Constructor.
     * 
     * @param aValue integer value.
     */
	public IP(int aValue) {
		super();
		this.value = aValue;
	}

	/**
     * Constructor.
     * 
     * @param aValue byte[4]
     */
	public IP(byte[] aValue) {
		this(parse(aValue));
	}

	/**
     * Constructor.
     * 
     * @param aValue ip-address
     * @throws IncorrectAddressException incorrect value.
     */
	public IP(String aValue) throws IncorrectAddressException {
		this(parse(aValue));
	}

	/**
     * Convert to integer.
     * 
     * @return integer value.
     */
	public int toInteger() {
		return this.value;
	}

	/**
     * Convert to bigendian bytes.
     * 
     * @return byte[4] bytes value.
     */
	public static byte[] toBytes(int value) {
		byte[] bytes = new byte[4];
		bytes[0] = (byte) ((value >> 24) & 0x0FF);
		bytes[1] = (byte) ((value >> 16) & 0x0FF);
		bytes[2] = (byte) ((value >> 8) & 0x0FF);
		bytes[3] = (byte) (value & 0x0FF);
		return bytes;
	}

	/**
     * Convert to bigendian bytes.
     * 
     * @return byte[4] bytes value.
     */
	public byte[] toBytes() {
		return toBytes(this.value);
	}

	/**
     * Convert to string.
     * 
     * @param value address value.
     * @return string value.
     */
	public static String toString(int value) {
		StringBuilder sb = new StringBuilder();
		sb.append((value >> 24) & 0x0FF);
		sb.append(DIV);
		sb.append((value >> 16) & 0x0FF);
		sb.append(DIV);
		sb.append((value >> 8) & 0x0FF);
		sb.append(DIV);
		sb.append(value & 0x0FF);
		return sb.toString();
	}

	/**
     * Convert to string.
     * 
     * @return string value.
     */
	@Override
	public String toString() {
		return toString(this.value);
	}

	/**
     * Convert bytes to integer value.
     * 
     * @param bytes byte[4] bytes value.
     * @return integer value.
     */
	public static int parse(byte[] bytes) {
		if (bytes == null || bytes.length != 4) throw new IllegalArgumentException("illegal bytes size");
		return ((bytes[0] & 0x0FF) << 24) | ((bytes[1] & 0x0FF) << 16) | ((bytes[2] & 0x0FF) << 8) | (bytes[3] & 0x0FF);
	}

	/**
     * Convert string to integer value. Строка имеет вид: <CODE>192.168.0.1</CODE><BR>
     * При преобразовании в целое, начало строки попадает в старшие разряды, а конец - в младшие.
     * 
     * @param value ip-адрес.
     * @return int целочисленное представление ip-адреса.
     * @throws IncorrectAddressException некорректная строка адреса.
     */
	public static int parse(String value) throws IncorrectAddressException {
		if (value == null || value.length() < 1) throw new IllegalArgumentException("empty value");
		// самый быстрый способ по преобразованию.
		char[] chars = null;
		int iip = 0;
		int pos = 0;
		int len = 0;
		chars = value.toCharArray();
		// first dec
		try {
			int dec = 0;
			while (pos + len < chars.length && chars[pos + len] != DIV) {
				len++;
			}
			if (pos + len >= chars.length || chars[pos + len] != DIV || len < 1 || len > 3) throw new IncorrectAddressException(
					value);
			dec = Integer.parseInt(new String(chars, pos, len), 10);
			if (dec < 0 || dec > 255) throw new IncorrectAddressException(value);
			iip = dec;
			// second dec
			pos += len + 1;
			len = 0;
			while (pos + len < chars.length && chars[pos + len] != DIV) {
				len++;
			}
			if (pos + len >= chars.length || chars[pos + len] != DIV || len < 1 || len > 3) throw new IncorrectAddressException(
					value);
			dec = Integer.parseInt(new String(chars, pos, len), 10);
			if (dec < 0 || dec > 255) throw new IncorrectAddressException(value);
			iip = (iip << 8) | dec;
			// third dec
			pos += len + 1;
			len = 0;
			while (pos + len < chars.length && chars[pos + len] != DIV) {
				len++;
			}
			if (pos + len >= chars.length || chars[pos + len] != DIV || len < 1 || len > 3) throw new IncorrectAddressException(
					value);
			dec = Integer.parseInt(new String(chars, pos, len), 10);
			if (dec < 0 || dec > 255) throw new IncorrectAddressException(value);
			iip = (iip << 8) | dec;
			// fourth dec
			pos += len + 1;
			len = 0;
			while (pos + len < chars.length) {
				len++;
			}
			if (pos + len != chars.length || len < 1 || len > 3) throw new IncorrectAddressException(value);
			dec = Integer.parseInt(new String(chars, pos, len));
			if (dec < 0 || dec > 255) throw new IncorrectAddressException(value);
			iip = (iip << 8) | dec;
		} catch (NumberFormatException ex) {
			throw new IncorrectAddressException(value, ex);
		}
		return iip;
	}

	/**
     * Compare objects.
     * 
     * @param obj object to compare
     * @return -1(&lt;), 0(==), 1(&gt;)
     */
	@Override
    public int compareTo(IP obj) {
		long thisip = ByteUtils.unsigned(this.value);
		long otherip = ByteUtils.unsigned(obj.value);
		if (thisip > otherip) return 1;
		else if (thisip < otherip) return -1;
		return 0;
	}

	/**
     * Make a clone.
     * 
     * @return IP clone.
     */
	@Override
	protected IP clone() {
		return new IP(this.value);
	}

	/**
     * @see java.lang.Object#hashCode()
     */
	@Override
	public int hashCode() {
		final int PRIME = 31;
		int result = 1;
		result = PRIME * result + this.value;
		return result;
	}

	/**
     * @see java.lang.Object#equals(java.lang.Object)
     */
	@Override
	public boolean equals(Object obj) {
		if (this == obj) return true;
		if (obj == null) return false;
		if (getClass() != obj.getClass()) return false;
		final IP other = (IP) obj;
		if (this.value != other.value) return false;
		return true;
	}
}
